package org.codefilarete.stalactite.engine.configurer.dslresolver;

import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;

/**
 * Creates a primary key equivalent to another one and creates a foreign key between them.
 *
 * @param <SRCTABLE> the type of the source table
 * @param <TARGETTABLE> the type of the target table
 * @param <I> the type of the primary key identifier
 * @author Guillaume Mary
 * @see #propagate(PrimaryKey, Table, ForeignKeyNamingStrategy)
 */
public class PrimaryKeyPropagator<SRCTABLE extends Table<SRCTABLE>, TARGETTABLE extends Table<TARGETTABLE>, I> {
	
	/**
	 * Creates a primary key on the given table by mimicking the given primary key.
	 * Also creates a foreign key between both.
	 * 
	 * @param primaryKey the template primary key that must be applied to the target table
	 * @param target target table on which primary key and foreign key must be added
	 * @param foreignKeyNamingStrategy the naming strategy to use for the foreign key to create
	 * @return the created foreign key
	 */
	public ForeignKey<TARGETTABLE, SRCTABLE, I> propagate(PrimaryKey<SRCTABLE, I> primaryKey,
	                                                      TARGETTABLE target,
	                                                      ForeignKeyNamingStrategy foreignKeyNamingStrategy) {
		// add primary key and foreign key to all tables
		projectPrimaryKey(primaryKey, target);
		return addForeignKey(target, primaryKey, foreignKeyNamingStrategy);
	}
	
	/**
	 * Creates foreign keys between given tables primary keys.
	 *
	 * @param from target tables on which foreign keys must be added, <strong>order matters</strong>
	 * @param to initial primary key on which the very first table primary key must point to
	 * @return the created foreign key
	 */
	private ForeignKey<TARGETTABLE, SRCTABLE, I> addForeignKey(TARGETTABLE from, PrimaryKey<SRCTABLE, I> to, ForeignKeyNamingStrategy foreignKeyNamingStrategy) {
		return addForeignKey(from.getPrimaryKey(), to, foreignKeyNamingStrategy);
	}
	
	/**
	 * Creates the primary key on the given table by creating the equivalent columns with the name and type of the given primary key.
	 *
	 * @param source the template primary key that must be applied to the target table
	 * @param table target table on which the primary key must be added
	 */
	private void projectPrimaryKey(PrimaryKey<SRCTABLE, I> source, TARGETTABLE table) {
		source.getColumns().forEach(pkColumn -> {
			// nullability = false may not be necessary because of primary key, let for principle
			Column<TARGETTABLE, ?> newColumn = table.addColumn(pkColumn.getName(), pkColumn.getJavaType(), pkColumn.getSize(), false);
			newColumn.primaryKey();
		});
	}
	
	private ForeignKey<TARGETTABLE, SRCTABLE, I> addForeignKey(PrimaryKey<TARGETTABLE, I> from, PrimaryKey<SRCTABLE, I> to, ForeignKeyNamingStrategy foreignKeyNamingStrategy) {
		return from.getTable().addForeignKey(foreignKeyNamingStrategy.giveName(from, to), from, to);
	}
}
